Skip to content

[Backend] Secure Refresh Token Rotation Mechanisms#323

Open
armorbreak001 wants to merge 1 commit intoGalactiGuild:mainfrom
armorbreak001:bounty/294-refresh-token
Open

[Backend] Secure Refresh Token Rotation Mechanisms#323
armorbreak001 wants to merge 1 commit intoGalactiGuild:mainfrom
armorbreak001:bounty/294-refresh-token

Conversation

@armorbreak001
Copy link
Copy Markdown

Fixes #294

What was done

Prisma Schema

  • Added RefreshToken model to schema.prisma:
    • token (unique, SHA-256 hashed) — opaque random string, NOT a JWT
    • userId → User relation with cascade delete
    • expiresAt — 7-day TTL
    • replacedBy — links to new token after rotation (for replay detection)
    • revokedAt — timestamp when token was invalidated
    • Indexes on userId and token for fast lookups

RefreshTokenService (src/auth/refresh-token.service.ts)

  • generate(userId): Creates a new opaque 64-byte hex token, stores SHA-256 hash in DB, returns raw token to client
  • rotate(rawToken): The core security feature — validates existing token → marks it as replaced (with replacedBy link) → issues brand new token atomically via Prisma transaction
  • Replay attack protection: If a revoked/replaced token is used again, the service detects reuse and revokes the entire token chain, forcing re-login
  • revoke(rawToken): Single token revocation (logout)
  • revokeAllForUser(userId): Bulk revocation (password change, security event)
  • cleanupExpired(): Remove expired tokens (for scheduled jobs)

AuthService Updates

  • All auth flows (login, register, walletAuth) now use RefreshTokenService.generate() instead of JWT-based refresh tokens
  • POST /auth/refresh now calls rotate() — each refresh gets a completely new token, old one is invalidated
  • Access token: 15 minutes TTL (short-lived)
  • Refresh token: 7 days TTL (opaque, rotated on each use)

How to verify

  1. Run cd backend && npx prisma db push to apply schema changes
  2. Register/login a user — response includes both accessToken and refreshToken
  3. Call POST /auth/refresh with {"refreshToken": "..."} — get a NEW refreshToken (old one is now invalid)
  4. Try using the OLD refresh token again → should get 403 Token reuse detected error
  5. Check refresh_tokens table in DB — see hashed tokens with revokedAt and replacedBy fields populated

Fixes GalactiGuild#294

- Add RefreshToken model to Prisma schema (opaque tokens, not JWTs)
- Create RefreshTokenService with full rotation logic:
  - Generate: random 64-byte hex strings, SHA-256 hashed in DB
  - Rotate: issue new token, invalidate old one (prevents replay attacks)
  - Replay detection: if revoked token is reused, revoke entire token chain
  - Revoke single or all tokens for a user
  - Cleanup expired tokens
- Update AuthService to use RefreshTokenService:
  - Login/Register/WalletAuth now generate opaque refresh tokens
  - POST /auth/refresh rotates tokens (new token issued, old invalidated)
  - Access token TTL: 15 minutes, Refresh token TTL: 7 days
- Tokens stored in dedicated refresh_tokens table, not on User model
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend] Secure Refresh Token Rotation Mechanisms

1 participant